//go:build windows
// +build windows

package printer

import (
	"errors"
	"sync"

	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"unsafe"

	"github.com/alexbrainman/printer"
	"golang.org/x/sys/windows"
)

var (
	manager PrinterManager
	once    sync.Once
	// 打印任务ID与文件名映射
	jobFileMap = struct {
		sync.RWMutex
		m map[int]string
	}{m: make(map[int]string)}
	modWinspool      = windows.NewLazySystemDLL("winspool.drv")
	procOpenPrinter  = modWinspool.NewProc("OpenPrinterW")
	procClosePrinter = modWinspool.NewProc("ClosePrinter")
)

func GetManager() PrinterManager {
	once.Do(func() {
		manager = newWindowsManager()
	})
	return manager
}

type windowsManager struct{}

func newWindowsManager() PrinterManager {
	return &windowsManager{}
}

func (w *windowsManager) ListPrinters() ([]Printer, error) {
	names, err := printer.ReadNames()
	if err != nil {
		return nil, err
	}
	list := []Printer{}
	for _, n := range names {
		list = append(list, Printer{Name: n, Status: "online", IsDefault: false})
	}
	// TODO: 标记默认打印机
	return list, nil
}

func (w *windowsManager) GetPrinterOptions(name string) (PrinterOptions, error) {
	// Windows下参数获取较复杂，暂返回常用参数
	return PrinterOptions{
		PaperSizes:   []string{"A4", "A5", "Letter"},
		Orientations: []string{"portrait", "landscape"},
		Copies:       CopiesRange{Min: 1, Max: 99},
	}, nil
}

// Windows API结构体和常量
const (
	FORM_PRINTER = 1
)

type FORM_INFO_1 struct {
	Flags         uint32
	pName         *uint16
	Size          SIZE
	ImageableArea RECT
}

type SIZE struct {
	cx int32 // 以千分之一毫米为单位
	cy int32
}

type RECT struct {
	left   int32
	top    int32
	right  int32
	bottom int32
}

func addCustomForm(printerName, formName string, widthMM, heightMM float64) error {
	hPrinter, err := openPrinterHandle(printerName)
	if err != nil {
		return err
	}
	defer closePrinterHandle(hPrinter)

	namePtr, _ := windows.UTF16PtrFromString(formName)
	form := FORM_INFO_1{
		Flags: 0,
		pName: namePtr,
		Size: SIZE{
			cx: int32(widthMM * 1000),
			cy: int32(heightMM * 1000),
		},
		ImageableArea: RECT{0, 0, int32(widthMM * 1000), int32(heightMM * 1000)},
	}
	mod, err := windows.LoadDLL("winspool.drv")
	if err != nil {
		return err
	}
	proc, err := mod.FindProc("AddFormW")
	if err != nil {
		return err
	}
	ret, _, callErr := proc.Call(
		uintptr(hPrinter),
		uintptr(FORM_PRINTER),
		uintptr(unsafe.Pointer(&form)),
	)
	if ret == 0 {
		return callErr
	}
	return nil
}

func deleteCustomForm(printerName, formName string) error {
	hPrinter, err := openPrinterHandle(printerName)
	if err != nil {
		return err
	}
	defer closePrinterHandle(hPrinter)
	namePtr, _ := windows.UTF16PtrFromString(formName)
	mod, err := windows.LoadDLL("winspool.drv")
	if err != nil {
		return err
	}
	proc, err := mod.FindProc("DeleteFormW")
	if err != nil {
		return err
	}
	ret, _, callErr := proc.Call(
		uintptr(hPrinter),
		uintptr(unsafe.Pointer(namePtr)),
	)
	if ret == 0 {
		return callErr
	}
	return nil
}

func openPrinterHandle(printerName string) (windows.Handle, error) {
	namePtr, _ := windows.UTF16PtrFromString(printerName)
	var h windows.Handle
	ret, _, err := procOpenPrinter.Call(
		uintptr(unsafe.Pointer(namePtr)),
		uintptr(unsafe.Pointer(&h)),
		0,
	)
	if ret == 0 {
		return 0, err
	}
	return h, nil
}

func closePrinterHandle(h windows.Handle) {
	procClosePrinter.Call(uintptr(h))
}

func (w *windowsManager) SubmitPrintJob(req PrintJobRequest) ([]int, error) {
	if len(req.Files) == 0 {
		return nil, errors.New("no files to print")
	}
	printerName := req.Printer
	formName := ""
	if req.Options["paperSize"] == "custom" {
		length, _ := req.Options["length"].(float64)
		width, _ := req.Options["width"].(float64)
		formName = fmt.Sprintf("Custom_%dx%d", int(width), int(length))
		if err := addCustomForm(printerName, formName, width, length); err != nil {
			return nil, fmt.Errorf("AddForm failed: %v", err)
		}
		defer deleteCustomForm(printerName, formName)
	}
	ids := []int{}
	for i, f := range req.Files {
		// 1. Base64解码
		data, err := base64.StdEncoding.DecodeString(f.ContentBase64)
		if err != nil {
			return nil, fmt.Errorf("base64 decode failed: %v", err)
		}
		// 2. 保存为临时文件
		tmpDir := os.TempDir()
		fileName := fmt.Sprintf("printjob_%d_%d.pdf", time.Now().UnixNano(), i)
		filePath := filepath.Join(tmpDir, fileName)
		err = ioutil.WriteFile(filePath, data, 0644)
		if err != nil {
			return nil, fmt.Errorf("write temp file failed: %v", err)
		}
		// 3. 打印前获取打印队列
		p, err := printer.Open(printerName)
		var beforeJobs []printer.JobInfo
		if err == nil {
			beforeJobs, _ = p.Jobs()
			p.Close()
		}
		// 4. 调用SumatraPDF命令行打印
		sumatraPath := "./SumatraPDF/SumatraPDF.exe" // 如有自定义路径请修改
		cmd := exec.Command(sumatraPath, "-print-to", printerName, filePath)
		err = cmd.Start()
		if err != nil {
			return nil, fmt.Errorf("启动打印命令失败: %v", err)
		}
		// 5. 打印后延迟获取打印队列，匹配新任务ID
		var jobId int
		for try := 0; try < 5; try++ {
			time.Sleep(2 * time.Second)
			p, err := printer.Open(printerName)
			if err != nil {
				continue
			}
			jobs, _ := p.Jobs()
			p.Close()
			// 找出新增任务
			for _, job := range jobs {
				found := false
				for _, b := range beforeJobs {
					if job.JobID == b.JobID {
						found = true
						break
					}
				}
				if !found {
					// 通过文档名或时间戳进一步匹配
					if job.DocumentName == fileName || job.DocumentName == f.Name {
						jobId = int(job.JobID)
						break
					}
				}
			}
			if jobId != 0 {
				break
			}
		}
		if jobId == 0 {
			// 匹配不到则用时间戳模拟
			jobId = int(time.Now().UnixNano()/1e6) + i
		}
		// 记录任务ID与文件名
		jobFileMap.Lock()
		jobFileMap.m[jobId] = fileName
		jobFileMap.Unlock()
		ids = append(ids, jobId)
		// 可选：延迟删除临时文件
		go func(p string) {
			time.Sleep(60 * time.Second)
			os.Remove(p)
		}(filePath)
	}
	return ids, nil
}

func (w *windowsManager) GetPrintJobStatus(jobId int) (PrintJobStatus, error) {
	jobFileMap.RLock()
	fileName := jobFileMap.m[jobId]
	jobFileMap.RUnlock()
	if fileName == "" {
		return PrintJobStatus{JobId: jobId, Status: "unknown", Message: "未找到任务"}, nil
	}
	// 遍历所有打印机，查找该任务
	printerNames, _ := printer.ReadNames()
	for _, pn := range printerNames {
		p, err := printer.Open(pn)
		if err != nil {
			continue
		}
		jobs, _ := p.Jobs()
		p.Close()
		for _, job := range jobs {
			if int(job.JobID) == jobId || job.DocumentName == fileName {
				status := "unknown"
				switch {
				case job.StatusCode&0x10 != 0:
					status = "printing"
				case job.StatusCode&0x08 != 0:
					status = "spooling"
				case job.StatusCode&0x200 != 0:
					status = "completed"
				case job.StatusCode&0x2 != 0:
					status = "failed"
				}
				return PrintJobStatus{JobId: jobId, Status: status, Message: job.Status}, nil
			}
		}
	}
	// 未找到，视为已完成
	return PrintJobStatus{JobId: jobId, Status: "completed", Message: "任务已完成或已被清理"}, nil
}
